package ch.epfl.lara.pm.parser;

import ch.epfl.lara.pm.ast._

object PrettyPrinter  {
  
  private val SPACE: String = " "
  
  private var indent = 0
  private val shift = 3
  
  private def inc = {
    indent += shift
    ""
  }
  
  private def dec = {
    indent -= shift
    if(indent < 0) throw new Exception("Wrong indentation")
    ""
  }
  
  private def mkIndent: String = {
    var buf = ""
    for(val i<- List.range(0,indent)) buf += " "
    buf
  }
  
  private def NL: String = "\n"+mkIndent
  
  private def NL2 = NL + NL
  
  def apply(tree: Tree) = println(format(tree))
  
  def apply(formula: Formula) = println(format(formula))
  
  def apply(warning: Warning): Unit = 
    println(format(warning))
  
  
  
  def format(separator: String)(trees: List[Tree]): String = trees match {
    case Nil => ""
    case x :: Nil => format(x)
    case x :: xs => format(x) + separator + format(separator)(xs)
  }
 
  def concat(trees: List[Tree]) = format("")(trees)
  def comma(trees: List[Tree]) = format(", ")(trees)
  def newline(trees: List[Tree]) = format("\n")(trees)
  def guard(tree: Option[Tree]) = optformat(" if ")(tree)
  
  
  def optformat(prefix: String)(tree: Option[Tree]): String = tree match {
    case None => ""
    case Some(term) => prefix + format(term)
  }
  
  def format(tree: Tree): String = tree match {
    case Program(constr,objs,clss,expr) => 
      format(constr) + NL + //"// extractor definition" + NL +
      concat(objs) + NL + //"// class definition" + NL +
      concat(clss) + NL + //"// main" + NL +
      format(expr)
    
    case ObjectDef(name,extr) => 
      "object " + name + " { " + inc + NL +
         format(extr) + dec + NL + 
      "}" + NL
    
    case ExtractorDef(arg,tpe,body) =>
      "def unapply(" + format(arg) + "): " + tpe + "= " + inc + NL + 
        format(body) + dec + NL
        
    case ParamDef(name,tpe) =>
      name + ": " + tpe
      
    case ClassDef(isAbs,name,fields,superc,methods) =>
      val abstr = if(isAbs) "abstract " else ""
      abstr + "class " + name + fields.map("val "+format(_)).mkString("(",",",")") + " extends " + superc + " { " + inc + NL + 
        concat(methods) + dec + NL + 
      "}" + NL
      
    case MethodDef(name,params,tpe,body) => 
      "def " + name + "(" + comma(params) + "): " + tpe + " = " + inc + NL + 
        format(body) + dec + NL
        
    case CaseDef(_,pattern,grd,body) => 
      "case " + format(pattern) + guard(grd) + " => " + 
        format(body) 
     
    case VarPattern(name,tpe) => name + " : " + tpe
    
    case ClassPattern(name,args) => name + "(" + comma(args) + ")"
    
    case Var(name) => name
    
    case True => "true"
      
    case False => "false"
      
    case Not(f) => "!("+format(f)+")"
    
    case Call(obj,method,args) => format(obj) + "." + method + "(" + comma(args) + ")"
      
    case Select(obj,field) => format(obj) + "." + field 
    
    case If(cond,then,elze) => 
      "if (" + format(cond) + ") {" + inc + NL + 
        format(then) + dec + NL + 
      "} else {" + inc + NL +
        format(elze) + dec + NL + 
      "}"
    
    case Match(selector,tpe,cases) =>
      "(" + "(" + format(selector) + ":" + tpe + ")"+ " match { " + inc + NL +
        format(NL)(cases) + dec + NL +
      "}" + ")"
        
    case New(name,params) => 
      "new " + name + "(" + comma(params) + ")"
        
    case INone => "None"
        
    case IsInstanceOf(e,tpe) =>
      format(e) + ".isInstanceOf[" + tpe + "]"
        
    case Number(n) => n+""
      
    case Plus(left,right) => "(" + format(left) + " + " + format(right) + ")" 
    
    case Minus(left,right) => "(" + format(left) + " - " + format(right) + ")" 
    
    case Prod(left,right) => "(" + format(left) + " * " + format(right) +")"
      
    case Div(left,right) => "(" + format(left) + " / " + format(right) +")"
      
    case Mod(left,right) => "(" + format(left) + " % " + format(right) +")"
        
    case Eq(left,right) => "(" + format(left) + " == " + format(right) + ")" 
    
    case Less(left,right) => "(" + format(left) + " < " + format(right) + ")" 
    
    case LessEq(left,right) => "(" + format(left) + " <= " + format(right)  + ")" 
    
    case EAnd(left,right) => "(" + format(left) + " && " + format(right) + ")" 
    
    case EOr(left,right) => "(" + format(left) + " || " + format(right) + ")" 
    
    case EImply(left,right) => "(" + format(left) + " --> " + format(right) + ")" 
    
    case NotEq(left,right) => "(" + format(left) + " != " + format(right) + ")" 
        
    case BoolType | WrongType | IntType => ""
     
    case ClassType(_) | OptionType(_) => ""
  }
  
  
  def format(xs: List[(String,Formula)]): String = xs match {
    case Nil => ""
    case (set,formula) :: rest =>
      val last = (if(rest.isEmpty) format(rest) else NL + format(rest))
      "/* constraints of "+set+" : " + NL + format(formula) + NL + " */" + last
  }
  
  
  def format(formula: Formula): String = formula match {
    case And(left,right) => format(left) + " & " + NL + format(right)
    case Or(left,right) => format(left) + " | " + NL + format(right)
    case SubsetEq(left,right) => "(" + format(left) + " <: " + format(right) + ")"
    case Union(left,right) => format(left) + " + " + format(right)
    case Intersection(left,right) => format(left) + " * " + format(right)
    case Set(x) => x
    case EmptySet => "empty"
    case _ => ""
  }
    

  private def format(warning: Warning): String = warning match {
    case DisjointnessFailed(cases) =>
      val message = "Checking disjointness FAILED.\n\n"
      var i = 0
      val failing = for((kase1,kase2)<-cases) yield {
        i += 1
        i+". The patterns declaration of \n\n" +
        "   " + "case " + format(kase1.pattern) + " " + guard(kase1.guard) + "\n" +
        "   " + "case " + format(kase2.pattern) + " " + guard(kase2.guard) + "\n" +
        "\n... may be overlapping."
      }
  
      failing.mkString(message,"\n\n","\n")
  
    case CompletenessFailed(incomplete) =>
      val message = "Checking completeness FAILED. Possible missing cases:\n"
      val orizontal_space = "   "
      incomplete.mkString(message+orizontal_space,"\n"+orizontal_space,"\n")
    
    case ReachabilityFailed(kase) =>
      val message = "Checking reachability FAILED.\n\n"
      message + 
      "The patterns declaration of \n\n" +
      "   case " + format(kase.pattern) + " " + guard(kase.guard) + "\n\n" +
      "... may be never reached.\n"      
        
    case ReachabilityExtractorsFailed(kase) =>
      val message = "Checking reachability FAILED.\n\n"
      message + 
      "Case \n\n" +
      "   case " + format(kase.pattern) + " " + guard(kase.guard) + "\n\n" +
      "can't be triggered because it doesn't belong to the scrutinee's type hierarchy.\n"      
        
    case NestedMatchesFailed(xs) =>
      val message = for((matchExpr,warning)<-xs ) yield {
        warning match {
          case NestedMatchesFailed(_) => format(warning)
          case _ => 
            "In nested matching expression:\n\n"+
            format(matchExpr) + "\n\n" + 
            format(warning) 
        }
      }
      
      message.mkString("\n\n")
  }

}
